1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package net.sf.webphotos.gui.util;
17
18 import java.awt.*;
19 import java.awt.event.*;
20 import java.util.*;
21 import java.util.List;
22 import javax.swing.*;
23 import javax.swing.event.TableModelEvent;
24 import javax.swing.event.TableModelListener;
25 import javax.swing.table.*;
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78 public final class TableSorter extends AbstractTableModel {
79
80 private static final long serialVersionUID = -5572044930561418466L;
81
82
83
84 protected TableModel tableModel;
85
86
87
88 public static final int DESCENDING = -1;
89
90
91
92 public static final int NOT_SORTED = 0;
93
94
95
96 public static final int ASCENDING = 1;
97 private static Directive EMPTY_DIRECTIVE = new Directive(-1, NOT_SORTED);
98
99
100
101 public static final Comparator<Comparable<Object>> COMPARABLE_COMPARATOR = new Comparator<Comparable<Object>>() {
102
103 @Override
104 public int compare(Comparable<Object> o1, Comparable<Object> o2) {
105 return o1.compareTo(o2);
106 }
107 };
108
109
110
111 public static final Comparator<Comparable<Object>> LEXICAL_COMPARATOR = new Comparator<Comparable<Object>>() {
112
113 @Override
114 public int compare(Comparable<Object> o1, Comparable<Object> o2) {
115 return o1.toString().compareTo(o2.toString());
116 }
117 };
118 private Row[] viewToModel;
119 private int[] modelToView;
120 private JTableHeader tableHeader;
121 private MouseListener mouseListener;
122 private TableModelListener tableModelListener;
123 private Map<Class<?>, Comparator<?>> columnComparators = new HashMap<Class<?>, Comparator<?>>();
124 private List<Directive> sortingColumns = new ArrayList<Directive>();
125
126
127
128
129 public TableSorter() {
130 this.mouseListener = new MouseHandler();
131 this.tableModelListener = new TableModelHandler();
132 }
133
134
135
136
137
138 public TableSorter(TableModel tableModel) {
139 this();
140 setTableModel(tableModel);
141 }
142
143
144
145
146
147
148 public TableSorter(TableModel tableModel, JTableHeader tableHeader) {
149 this();
150 setTableHeader(tableHeader);
151 setTableModel(tableModel);
152 }
153
154 private void clearSortingState() {
155 viewToModel = null;
156 modelToView = null;
157 }
158
159
160
161
162
163 public TableModel getTableModel() {
164 return tableModel;
165 }
166
167
168
169
170
171 public void setTableModel(TableModel tableModel) {
172 if (this.tableModel != null) {
173 this.tableModel.removeTableModelListener(tableModelListener);
174 }
175
176 this.tableModel = tableModel;
177 if (this.tableModel != null) {
178 this.tableModel.addTableModelListener(tableModelListener);
179 }
180
181 clearSortingState();
182 fireTableStructureChanged();
183 }
184
185
186
187
188
189 public JTableHeader getTableHeader() {
190 return tableHeader;
191 }
192
193
194
195
196
197 public void setTableHeader(JTableHeader tableHeader) {
198 if (this.tableHeader != null) {
199 this.tableHeader.removeMouseListener(mouseListener);
200 TableCellRenderer defaultRenderer = this.tableHeader.getDefaultRenderer();
201 if (defaultRenderer instanceof SortableHeaderRenderer) {
202 this.tableHeader.setDefaultRenderer(((SortableHeaderRenderer) defaultRenderer).tableCellRenderer);
203 }
204 }
205 this.tableHeader = tableHeader;
206 if (this.tableHeader != null) {
207 this.tableHeader.addMouseListener(mouseListener);
208 this.tableHeader.setDefaultRenderer(
209 new SortableHeaderRenderer(this.tableHeader.getDefaultRenderer()));
210 }
211 }
212
213
214
215
216
217 public boolean isSorting() {
218 return !sortingColumns.isEmpty();
219 }
220
221 private Directive getDirective(int column) {
222 for (int i = 0; i < sortingColumns.size(); i++) {
223 Directive directive = (Directive) sortingColumns.get(i);
224 if (directive.column == column) {
225 return directive;
226 }
227 }
228 return EMPTY_DIRECTIVE;
229 }
230
231
232
233
234
235
236 public int getSortingStatus(int column) {
237 return getDirective(column).direction;
238 }
239
240 private void sortingStatusChanged() {
241 clearSortingState();
242 fireTableDataChanged();
243 if (tableHeader != null) {
244 tableHeader.repaint();
245 }
246 }
247
248
249
250
251
252
253 public void setSortingStatus(int column, int status) {
254 Directive directive = getDirective(column);
255 if (directive != EMPTY_DIRECTIVE) {
256 sortingColumns.remove(directive);
257 }
258 if (status != NOT_SORTED) {
259 sortingColumns.add(new Directive(column, status));
260 }
261 sortingStatusChanged();
262 }
263
264
265
266
267
268
269
270 protected Icon getHeaderRendererIcon(int column, int size) {
271 Directive directive = getDirective(column);
272 if (directive == EMPTY_DIRECTIVE) {
273 return null;
274 }
275 return new Arrow(directive.direction == DESCENDING, size, sortingColumns.indexOf(directive));
276 }
277
278 private void cancelSorting() {
279 sortingColumns.clear();
280 sortingStatusChanged();
281 }
282
283
284
285
286
287
288 public void setColumnComparator(Class<?> type, Comparator<?> comparator) {
289 if (comparator == null) {
290 columnComparators.remove(type);
291 } else {
292 columnComparators.put(type, comparator);
293 }
294 }
295
296
297
298
299
300
301 @SuppressWarnings("unchecked")
302 protected Comparator<Comparable<Object>> getComparator(int column) {
303 Class<?> columnType = tableModel.getColumnClass(column);
304 Comparator<Comparable<Object>> comparator = (Comparator<Comparable<Object>>) columnComparators.get(columnType);
305 if (comparator != null) {
306 return comparator;
307 }
308 if (Comparable.class.isAssignableFrom(columnType)) {
309 return COMPARABLE_COMPARATOR;
310 }
311 return LEXICAL_COMPARATOR;
312 }
313
314 private Row[] getViewToModel() {
315 if (viewToModel == null) {
316 int tableModelRowCount = tableModel.getRowCount();
317 viewToModel = new Row[tableModelRowCount];
318 for (int row = 0; row < tableModelRowCount; row++) {
319 viewToModel[row] = new Row(row);
320 }
321
322 if (isSorting()) {
323 Arrays.sort(viewToModel);
324 }
325 }
326 return viewToModel;
327 }
328
329
330
331
332
333
334 public int modelIndex(int viewIndex) {
335 return getViewToModel()[viewIndex].modelIndex;
336 }
337
338 private int[] getModelToView() {
339 if (modelToView == null) {
340 int n = getViewToModel().length;
341 modelToView = new int[n];
342 for (int i = 0; i < n; i++) {
343 modelToView[modelIndex(i)] = i;
344 }
345 }
346 return modelToView;
347 }
348
349
350 @Override
351 public int getRowCount() {
352 return (tableModel == null) ? 0 : tableModel.getRowCount();
353 }
354
355 @Override
356 public int getColumnCount() {
357 return (tableModel == null) ? 0 : tableModel.getColumnCount();
358 }
359
360 @Override
361 public String getColumnName(int column) {
362 return tableModel.getColumnName(column);
363 }
364
365 @Override
366 public Class<?> getColumnClass(int column) {
367 return tableModel.getColumnClass(column);
368 }
369
370 @Override
371 public boolean isCellEditable(int row, int column) {
372 return tableModel.isCellEditable(modelIndex(row), column);
373 }
374
375 @Override
376 public Object getValueAt(int row, int column) {
377 return tableModel.getValueAt(modelIndex(row), column);
378 }
379
380 @Override
381 public void setValueAt(Object aValue, int row, int column) {
382 tableModel.setValueAt(aValue, modelIndex(row), column);
383 }
384
385
386 private class Row implements Comparable<Row> {
387
388 private int modelIndex;
389
390 public Row(int index) {
391 this.modelIndex = index;
392 }
393
394 @SuppressWarnings("unchecked")
395 @Override
396 public int compareTo(Row o) {
397 int row1 = modelIndex;
398 int row2 = o.modelIndex;
399
400 for (Iterator<Directive> it = sortingColumns.iterator(); it.hasNext();) {
401 Directive directive = (Directive) it.next();
402 int column = directive.column;
403 Object o1 = tableModel.getValueAt(row1, column);
404 Object o2 = tableModel.getValueAt(row2, column);
405
406 int comparison = 0;
407
408 if (o1 == null && o2 == null) {
409 comparison = 0;
410 } else if (o1 == null) {
411 comparison = -1;
412 } else if (o2 == null) {
413 comparison = 1;
414 } else {
415 comparison = getComparator(column).compare((Comparable<Object>) o1, (Comparable<Object>) o2);
416 }
417 if (comparison != 0) {
418 return directive.direction == DESCENDING ? -comparison : comparison;
419 }
420 }
421 return 0;
422 }
423 }
424
425 private class TableModelHandler implements TableModelListener {
426
427 @Override
428 public void tableChanged(TableModelEvent e) {
429
430 if (!isSorting()) {
431 clearSortingState();
432 fireTableChanged(e);
433 return;
434 }
435
436
437
438
439 if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {
440 cancelSorting();
441 fireTableChanged(e);
442 return;
443 }
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463 int column = e.getColumn();
464 if (e.getFirstRow() == e.getLastRow()
465 && column != TableModelEvent.ALL_COLUMNS
466 && getSortingStatus(column) == NOT_SORTED
467 && modelToView != null) {
468 int viewIndex = getModelToView()[e.getFirstRow()];
469 fireTableChanged(new TableModelEvent(TableSorter.this,
470 viewIndex, viewIndex,
471 column, e.getType()));
472 return;
473 }
474
475
476 clearSortingState();
477 fireTableDataChanged();
478 }
479 }
480
481 private class MouseHandler extends MouseAdapter {
482
483 @Override
484 public void mouseClicked(MouseEvent e) {
485 JTableHeader h = (JTableHeader) e.getSource();
486 TableColumnModel columnModel = h.getColumnModel();
487 int viewColumn = columnModel.getColumnIndexAtX(e.getX());
488 int column = columnModel.getColumn(viewColumn).getModelIndex();
489 if (column != -1) {
490 int status = getSortingStatus(column);
491 if (!e.isControlDown()) {
492 cancelSorting();
493 }
494
495
496 status = status + (e.isShiftDown() ? -1 : 1);
497 status = (status + 4) % 3 - 1;
498 setSortingStatus(column, status);
499 }
500 }
501 }
502
503 private static class Arrow implements Icon {
504
505 private boolean descending;
506 private int size;
507 private int priority;
508
509 public Arrow(boolean descending, int size, int priority) {
510 this.descending = descending;
511 this.size = size;
512 this.priority = priority;
513 }
514
515 @Override
516 public void paintIcon(Component c, Graphics g, int x, int y) {
517 Color color = c == null ? Color.GRAY : c.getBackground();
518
519
520 int dx = (int) (size / 2 * Math.pow(0.8, priority));
521 int dy = descending ? dx : -dx;
522
523 y = y + 5 * size / 6 + (descending ? -dy : 0);
524 int shift = descending ? 1 : -1;
525 g.translate(x, y);
526
527
528 g.setColor(color.darker());
529 g.drawLine(dx / 2, dy, 0, 0);
530 g.drawLine(dx / 2, dy + shift, 0, shift);
531
532
533 g.setColor(color.brighter());
534 g.drawLine(dx / 2, dy, dx, 0);
535 g.drawLine(dx / 2, dy + shift, dx, shift);
536
537
538 if (descending) {
539 g.setColor(color.darker().darker());
540 } else {
541 g.setColor(color.brighter().brighter());
542 }
543 g.drawLine(dx, 0, 0, 0);
544
545 g.setColor(color);
546 g.translate(-x, -y);
547 }
548
549 @Override
550 public int getIconWidth() {
551 return size;
552 }
553
554 @Override
555 public int getIconHeight() {
556 return size;
557 }
558 }
559
560 private class SortableHeaderRenderer implements TableCellRenderer {
561
562 private TableCellRenderer tableCellRenderer;
563
564 public SortableHeaderRenderer(TableCellRenderer tableCellRenderer) {
565 this.tableCellRenderer = tableCellRenderer;
566 }
567
568 @Override
569 public Component getTableCellRendererComponent(JTable table,
570 Object value,
571 boolean isSelected,
572 boolean hasFocus,
573 int row,
574 int column) {
575 Component c = tableCellRenderer.getTableCellRendererComponent(table,
576 value, isSelected, hasFocus, row, column);
577 if (c instanceof JLabel) {
578 JLabel l = (JLabel) c;
579 l.setHorizontalTextPosition(JLabel.LEFT);
580 int modelColumn = table.convertColumnIndexToModel(column);
581 l.setIcon(getHeaderRendererIcon(modelColumn, l.getFont().getSize()));
582 }
583 return c;
584 }
585 }
586
587 private static class Directive {
588
589 private int column;
590 private int direction;
591
592 public Directive(int column, int direction) {
593 this.column = column;
594 this.direction = direction;
595 }
596 }
597 }